Seznami I

Skop vaj, pri katerih uporabljamo sezname


Kamionisti

V Republiki Banana zaradi utrujenosti in pomanjkanja koncentracije kamionisti (pet minut za učenje slovenčine) vse pogosteje povzročajo nesreče. Na vladi so se odločili, da bodo sprejeli zakone, ki bodo uredili problematiko preutrujenih kamionistov. Najprej pa morajo analitiki preučiti njihove navade. V ta namen so pridobili podatke o kamionistih. Za vsakega imajo podan seznam, v katerem so shranjene prevožene razdalje po posameznih dnevih. Število 0 pomeni, da je kamionist tisti dan počival. Na primer seznam

[350, 542.5, 0, 602, 452.5, 590.5, 0, 248]

pomeni, da je kamionist prvi dan prevozil 350 km, drugi dan je prevozil 542.5 km, tretji dan je počival itd.

1. podnaloga

Sestavite funkcijo pocitekInPovprecje(voznja), ki kot argument dobi zgoraj opisani seznam (prevožene razdalje po posameznih dnevih) in vrne par števil. Prvo število naj bo število dni, ko je kamionist počival. Drugo število naj bo na eno decimalko zaokrožena povprečna dnevna prevožena razdalja, pri čemer upoštevamo samo tiste dneve, ko ni počival. Predpostavite lahko, da kamionist vsaj en dan ni počival. Primer:

>>> pocitekInPovprecje([200, 300, 0, 100])
(1, 200.0)

Kamionist je torej enkrat počival. Ob dnevih, ko ni počival, pa je v povprečju prevozil 200 km.

Uradna rešitev

def pocitekInPovprecje(voznja):
    '''Vrne par števil. Prvo pove število dni, ko kamionist počiva in drugo
    povprečno št. kilometrov na dneve vožnje '''
    stPocitkov = 0
    delovnihDni = 0
    skupnoKilometrov = 0
    for prevozenoTaDan in voznja:
        if prevozenoTaDan == 0:
            stPocitkov += 1
        else:
            delovnihDni += 1
        skupnoKilometrov += prevozenoTaDan
    return stPocitkov, round(skupnoKilometrov / delovnihDni, 1) # zaradi predpostavke
                                           # zagotovo ne delimo z 0!

2. podnaloga

Napišite funkcijo primerjaj(prvi, drugi), ki primerja vožnjo dveh kamionistov. Funkcija kot argumenta dobi dva enako dolga seznama prvi in drugi, ki opisujeta vožnjo dveh kamionistov, ki sta se podala na isto pot. Funkcije naj sestavi in vrne nov seznam, v katerem je za vsak dan zapisano, kdo je do tega dne skupaj prevozil večjo razdaljo: 1, če je bil to 1. kamionist; 2, če je bil to 2. kamionist in 0, če sta oba prevozila enako razdaljo. Primer:

>>> primerjaj([200, 300, 100], [200, 200, 300])
[0, 1, 2]
>>> primerjaj([500, 100, 100], [100, 200, 200])
[1, 1, 1]

Uradna rešitev

def primerjaj(prvi, drugi):
    '''Vrne seznam podatkov o tem, kateri kamionist je prišel do določenega dne dlje'''
    prevozenoPrvi = 0    # skupno število prevoženih kilometrov do določenega dne
    prevozenoDrugi = 0
    primerjava = []
    dan = 0
    while dan < len(prvi):  # uporabimo while, ker potrebujemo podatke iz obeh seznamov
        prevozenoPrvi += prvi[dan]
        prevozenoDrugi += drugi[dan]
        # kdo je v tem trenutku že dlje
        if prevozenoPrvi > prevozenoDrugi:
            primerjava.append(1)
        elif prevozenoPrvi < prevozenoDrugi:
            primerjava.append(2)
        else:
            primerjava.append(0)
        dan += 1
    return primerjava

3. podnaloga

Vlada je uzakonila naslednja pravila: Povprečna prevožena razdalja v treh zaporednih dneh ne sme biti več kot 500 km. (Štejemo tudi dneve, ko je kamionist počival.) Kamionist ne sme brez počitka voziti več kot 5 dni v zaporedju.

Ker pa so ugotovili, da so kamionisti prevečkrat svoje zaporedje skrajšali na dva dni in se s tem dejansko izognili pravilu, so uzakonili še: Zadnji dan ne sme voziti več kot 500 km. Povprečje zadnjih dveh dni ne sme biti več kot 500 km.

Sestavite funkcijo poPravilih(voznja), ki vrne True, če se je kamionist držal predpisov, in False, če se jih ni.

>>> poPravilih([50, 200, 300, 20, 100, 60])
False
>>> poPravilih([600, 200, 0, 300, 300, 0, 600, 600, 400])
False
>>> poPravilih([600, 600, 0, 600, 600])
False
>>> poPravilih([600, 600, 0, 200, 600])
False

Uradna rešitev

def poPravilih(voznja):
    '''Ali je vožnja potekala po pravilih'''
    zaporedno = 0
    dan = 0 
    while dan < len(voznja) - 2: # za račun povprečja zaporedja!
        prevozil = voznja[dan]
        if prevozil == 0:   # je počival, začnemo šteti od začetka
            zaporedno = 0
        else:
            zaporedno += 1
        if zaporedno > 5:  # zaporedoma vozi več kot 5 dni
            return False
        povprecje = (voznja[dan] + voznja[dan+1] + voznja[dan+2]) / 3
        if povprecje > 500:
            return False
        dan += 1
    # pregledamo še zadnja dva dni
  
    while dan < len(voznja): # dan je že ustrezno "velik", torej se bo zanka izvedla 2krat
        prevozil = voznja[dan]
        if prevozil == 0:
            zaporedno = 0
        else:
            zaporedno += 1
        if zaporedno > 5:
            return False
        dan += 1
    # še povprečji

    # če imamo sploh še 2 dni   (če imamo le en podatek+, to ne bo res)
    if len(voznja) - dan == 2:
        if (voznja[len(voznja) - 1] + voznja[len(voznja) - 2])/2 > 500:
            return False
    # še kontrola dolžine vožnje zadnji dan
    if voznja[-1] > 500:
        return False
    # uspešno preko vseh kontrol
    return True

4. podnaloga

Podatke za več kamionistov so analitiki združili v en sam seznam. Zgled takega seznama:

vsiSkupaj = [
    [50, 200, 300, 20, 100, 60],
    [600, 600, 0, 600, 600, 0, 500, 500],
    [600, 200, 0, 300, 300, 0, 600, 600, 400]
]

Sestavite funkcijo preveriVse(seznamVozenj), ki dobi kot argument na tak način opisan seznam in vrne seznam logičnih vrednosti, ki za vsakega kamionista pove, če je vozil po predpisih. Primer:

>>> preveriVse(vsiSkupaj)
[False, True, False]

Uradna rešitev

def preveriVse(seznamVozenj):
    '''Preveri vožnje vseh kamionistov'''
    rezultat = []
    for voznjaPosameznika in seznamVozenj: # 
        rezultat.append(poPravilih(voznjaPosameznika))
    return rezultat

5. podnaloga

Ko so vožnje vseh kamionistov že tako lepo združene, so se analitiki spravili računati še to, kakšno je navečje število voženj, ki jih je nekdo opravil, kakšno je največje število dni počitka in kakšno največje in kakšno najmanjše povprečno povprečno število dnevno prevoženih kilometrov (povprečje tako, kot pri prvi nalogi - štejemo le dneve vožnje). Pri tem upoštevamo samo tiste kamioniste, ki so vozili po pravilih! Sestavite funkcijo analitika(seznamVozenj), ki dobi kot argument opisani seznam in vrne nabor 4 vrednosti: največ voženj, največ dni počitka, maksimalno dnevno prevoženih in minimalno dnevno prevoženih km. Predpostavi, da imamo vsaj enega kamionista, ki je vozil po predpisih! Primer (podatki iz prejšnje naloge):

>>> analitika(vsiSkupaj)
(6, 2, 566.7, 566.7)

Uradna rešitev

def analitika(seznamVozenj):
    '''med pravilno vozečimi kamionisti
       poišče nabor 4 vrednosti: največ voženj, največ dni počitka, maksimalno dnevno prevoženih
       in minimalno dnevno prevoženih km.'''
    minPovp = 600 # vsaj en bo vozil po predpisih, torej bo minimalno povprecje zagotovo pod 500!
    maxPovp = 0
    maxPoc = 0 # maksimalno dni počitka
    maxVozenj = 0
    for voznjaPosameznika in seznamVozenj:
        if poPravilih(voznjaPosameznika): # "dobri" voznik, ga upoštevamo
            poc, povp = pocitekInPovprecje(voznjaPosameznika)
            vozenj = len(voznjaPosameznika) - poc
            # ali so katere od teh vrednosti za "naj"
            if poc > maxPoc:
                maxPoc = poc
            # namesto if, uporabimo raje kar min, oz. max
            maxVozenj = max(vozenj, maxVozenj)
            minPovp = min(povp, minPovp)
            maxPovp = max(povp, maxPovp)
    return (maxVozenj, maxPoc, maxPovp, minPovp)

aN ebor

Na Najboljši TV je prava zmešnjava (kar za najnovejšo TV v Sloveniji niti ni tako nenavadno). Le še 5 minut do začetka oddaje Izbiramo najlepšega bika Slovenije. Težav pa cel kup.

1. podnaloga

Prva težava je, da je na vseh seznamih naveden napačni vrstni red imen bikov. Režiser na vsak način poskuša prepričati voditeljski par, da ni nič narobe, le od spodaj navzgor naj bereta razvrstitev. A kljub temu, da sta to daleč najbolj inteligentna TV voditelja v Sloveniji, se bojita, da bo to zanju pretežka naloga. Zato jim moraš hitro napisati funkcijo obrniSeznamBikov(sezBik), ki vrnila nov seznam teh imen v obratnem vrstnem redu. Za seznam

    ['Bukso', 'Hitri', 'Šeko', 'Lisko']

naj vrne seznam

    ['Lisko', 'Šeko', 'Hitri', 'Bukso']

Pazi, ker moraš vrniti nov seznam, uporabe metode reverse ne bo OK!

Uradna rešitev

def obrniSeznamBikov(sezBik):
    ''' vrne nov seznam bikov v obratnem vrstnem redu '''
    novSez = []
    for bik in sezBik:
        novSez = [bik] + novSez # ker dodajamo spredaj, bomo seznam obrnili
    return novSez

2. podnaloga

Seznam bikov je sedaj urejen. A kaj, ko so na seznam prikradle tudi krave (na TV sumijo, da gre za podtalno delovanje stricev iz ozadja, a ...). In zato se bodo pritožili na MCSodišče. A ura je neizprosna, ustrezen seznam potrebujejo takoj. Na srečo je najomiljeni voditelj z vso svojo avtoriteto ugotovil, da je krave zlahka prepoznati, saj se vsa njihova imena končajo z 'a'. Ker želijo ohraniti dokaze, morajo seznam s pomešanimi kravami ohraniti. Zato sestavi funkcijo odstraniKrave(seznam), ki vrnila nov seznam teh imen brez krav. Biki morajo ohraniti svoj vrstni red. Za seznam

    ['Liska', 'Šeko', 'Hitri', 'Buksa']

funkcija vrne

    ['Šeko', 'Hitri']

Uradna rešitev

def odstraniKrave(seznam):
    ''' vrne nov seznam brez krav (imen, ki se končajo z a) '''
    novSez = []
    for žival in seznam:
        if žival[-1] != 'a' : # ime se ne končna z a
            novSez.appendival)
    return novSez

3. podnaloga

težav pa še ni konec. Vse to prekladanje seznamov je povzročilo, da so sedaj določena imena v seznamu navedena večkrat. Zato napiši funkcijo odstraniOdvečne(seznam), ki vrne nov seznam, a tako, da iz seznama pomeče vsa odvečna imena. Pri enakih imenih ohrani le tistega, ki se je v seznamu pojavil prvi.m Iz

    ['Lisko', 'Šeko', 'Šeko', 'Šeko', 'Hitri', 'Bukso', 'Lisko']

dobimo

    ['Lisko', 'Šeko', 'Hitri', 'Bukso']

Namig: Kaj naredi 'bla' in ['blu', 'ble', 'bla', 'blu']?

Uradna rešitev

def odstraniOdvečne(seznam):
    ''' vrne nov seznam brez odvečnih (podvojenih) imen '''
    novSez = []
    for žival in seznam:
        if žival not in novSez : # ime se še ni pojavilo
            novSez.appendival)
    return novSez

Čete

Četa je najdaljše možno strnjeno podzaporedje v danem seznamu števil z določeno lastnostjo. Če je zaporedje naraščajoče (vsak naslednji element podzaporedja je večji), govorimo o naraščajočih četah, če je nepadajoče (torej je vsak naslednji element večji ali enak) o nepadajočih četah ...

Celoten seznam je tako sestavljen iz več zaporednih čet. Predpostavili bomo, da bomo vedno upoštevali čete iste vrste. Tako v seznamu [2,5,7,1,45,7,7,15] najdemo kar 4 naraščajoče čete (2,5,7, 1,45, 7 in 7,15), seznam [3,5,8,10,12] pa je sestavljen iz ene same čete.

Po drugi strani pa v seznamu [2,5,7,1,45,7,7,15] najdemo kar 6 padajočih čete (2, 5, 7, 1, 45, 7, 7 in 15) in 5 nenaraščajočih čet (2, 5, 7, 1, 45, 7, 7 in 15)

1. podnaloga

Sestavite funkcijo naraščajoč(seznam), ki preveri, ali elementi seznama seznam tvorijo narascajoče zaporedje.

>>> naraščajoč([])
True
>>> naraščajoč([1, 2, 5, 8, 12, 35])
True
>>> naraščajoč([3, 5, 5])
False
>>> naraščajoč([2, 6, 4, 8, 9, 6])
False

Uradna rešitev

def naraščajoč(seznam):
    '''Ali vsi elementi seznama naraščajo'''
    if seznam == []: return True # na "prazno" res
    for i in range(1, len(seznam)):
        if seznam[i - 1] >= seznam[i]: return False 
    return True # vse kontrole padanja so bile neuspešne, narašča!

2. podnaloga

Sestavi funkcijo številoNepadajočihČet(sez), ki v danem številskem seznamu prešteje, iz koliko nepadajočih čet je sestavljen. Nepadajoča četa je najdaljši možen nepadajoče urejen podseznam, ali še enostavneje, dokler se zaporedni elementi seznama ne zmanjšujejo, so v isti četi.

 >>> številoNepadajočihČet([])
 0
 >>> številoNepadajočihČet([6])
 1
 >>> številoNepadajočihČet([1, 3, 4, 7, 23, 56, 81])
 1
 >>> številoNepadajočihČet([1, 2, 2, 6, 2, 3, 7, 5, 2, 3, 8])
 4

Uradna rešitev

def številoNepadajočihČet(seznam):
    ''' koliko nepadajočih čet ima seznam '''
    if seznam == []:
        return 0
    stCet = 0
    prejšni = seznam[0] # ker NE velja x < x ne bo težav z začetkom!
    for x in seznam: 
        if x < prejšni: # konec čete, začetek nove
            stCet += 1
        prejšni = x
    return stCet + 1 # 1 zaradi zadnje!

3. podnaloga

Sestavi funkcijo številoNaraščajočihČet(sez), ki v danem številskem seznamu prešteje, iz koliko naraščajočih čet je sestavljen. Naraščajoča četa je najdaljši možen naraščajoče urejen podseznam, ali še enostavneje: dokler zaporedni elementi seznama naraščajo, so v isti četi.

 >>> številoNaraščajočihČet([])
 0
 >>> številoNaraščajočihČet([6])
 1
 >>> številoNaraščajočihČet([1, 3, 4, 7, 23, 56, 81])
 1
 >>> številoNaraščajočihČet([1, 2, 2, 6, 2, 3, 7, 5, 2, 3, 8])
 5

Uradna rešitev

def številoNaraščajočihČet(seznam):
    ''' koliko naraščajočih čet ima seznam '''
    if seznam == []:
        return 0
    stCet = 0
    prejšni = seznam[0]
    for x in seznam:
        if x <= prejšni: # konec čete, začetek nove
            stCet += 1
        prejšni = x
    return stCet

4. podnaloga

Sestavite funkcijo dolžinaNajdaljšeNaraščajočeČete(seznam), ki ugotovi, kakšna je dolžina najdaljše čete v seznamu seznam.

>>> dolžinaNajdaljšeNaraščajočeČete([])
0
>>> dolžinaNajdaljšeNaraščajočeČete([2, 5, 7, 10])
4
>>> dolžinaNajdaljšeNaraščajočeČete([1, 3, 6, 3, 8, 8, 10, 12])
3

Uradna rešitev

def dolžinaNajdaljšeNaraščajočeČete(seznam):
    '''Kako dolga je najdaljša naraščajoča četa'''
    if seznam == []: return 0
    naj = 0 # največja dolžina do sedaj
    dolzina = 1 # dolžina trenutne čete (vsebuje prvi element)
    for i in range(1, len(seznam)): # od drugega elementa naprej
        if seznam[i - 1] < seznam[i]:
            dolzina += 1 # še vedno imamo isto četo
        else: # naleteli smo na novo četo
            if dolzina > naj: # je morda ravno zaključena četa daljša?
                naj = dolzina
            dolzina = 1 # začeli smo na novo
    return max(naj, dolzina) #paziti moramo še na dolžino zadnje

5. podnaloga

Sestavite funkcijo naraščajočeČete(seznam), ki vrne seznam vseh naraščajočih čet, ki tvorijo seznam seznam.

>>> naraščajočeČete([1, 3, 6, 3, 8, 8, 10, 12])
[[1, 3, 6], [3, 8], [8, 10, 12]]

Uradna rešitev

def naraščajočeČete(seznam):
    '''Seznam vseh naraščajočih čet v seznamu'''
    if seznam == []: return [] 
    seznamCet = []
    ceta = [seznam[0]] # trenutna četa
    for i in range(1, len(seznam)):
        if seznam[i - 1] < seznam[i]:
            ceta.append(seznam[i]) # podaljšujemo obstoječo četo
        else: # zaključili smo s prejšnjo
            seznamCet.append(ceta)
            ceta = [seznam[i]] # in začeli novo
    seznamCet.append(ceta) # dodamo še zadnjo četo
    return seznamCet

Polžje dirke

Butalci vsako leto priredijo polžje dirke. Pomagajte jim pri organizaciji.

1. podnaloga

Tekmovalnega polža opišemo s tremi parametri: starostjo, težo in velikostjo. Faktor njegove tekmovalne sposobnosti opisuje takšna formula: $faktor = (starost + (\frac{teža}{velikost})^2)^2$, pri čemer velja manj je bolje (manjša kot je vrednost faktorja, boljši je polž).

Med vsemi polži želimo poiskati najboljšega. Napišite funkcijo najboljsi(s), ki sprejme seznam opisov polžev (seznam trojčkov [starost, teža, velikost]) in vrne faktor za najboljšega polža, spet na dve decimalni mesti natančno.

>>> najboljsi([[5, 6, 72], [4, 17, 77], [14, 21, 22], [17, 36, 64], [13, 29, 23]])
16.39

Uradna rešitev

def sposobnost(s, t, v):
    '''Vrne sposobnost polža'''
    return round((s + (t/v)**2)**2,2)

def najboljsi(polzi):
    '''Določi faktor sposobnosti najboljšega polža'''
    polz = polzi[0] # prvi polž
    najboljsiFaktor = sposobnost(polz[0], polz[1], polz[2]) # prvi je najboljši ;-)
    for polz in polzi: # prvega bomo sicer gledsali dvakrat, a kaj zato!
        kakoSposoben = sposobnost(polz[0], polz[1], polz[2])
        if kakoSposoben < najboljsiFaktor: # našli smo boljšega
            najboljsiFaktor = kakoSposoben
    return najboljsiFaktor
        
# Mimogrede: z nekaj znanja /ki ga še nimamo/ bi nalogo lahko rešili
# v eni vrstici ;-)
##def najboljsi(s):
##    return min([sposobnost(x[0], x[1], x[2]) for x in s])

2. podnaloga

Butalski polži se premikajo samo v štirih smereh: sever, jug, vzhod in zahod, kar v koordinatnem sistemu pomeni gor, dol, desno in levo. V vsakem koraku se premaknejo za eno enoto, lahko pa tudi mirujejo. Njihove premike zato lahko opišemo s seznami, ki vsebujejo poljubno zaporedje črk 'S', 'J', 'V', 'Z' in 'M'.

Napišite funkcijo pot(premiki), ki sprejme seznam, ki opisuje premike polža in pove, kako daleč je polž lezel in kako daleč (zračne razdalje) je prilezel. Rezultat naj bo dan v obliki para celo in dec. število, kjer je slednje zaokroženo na dve decimalni mesti.

>>> pot(['M', 'J', 'V', 'S', 'S', 'M'])
(4, 1.41)

Uradna rešitev

def pot(premiki):
    '''Kako daleč je lezel in prilezel polž'''
    x=0
    y=0
    kolikoMirovanj = 0
    for premik in premiki: # izračunamo končno točko, ter število mirovanj
        if premik == 'S':
            y += 1
        elif premik == 'J':
            y -= 1
        elif premik == 'V':
            x += 1
        elif premik == 'Z':
            x -= 1
        else:
            kolikoMirovanj += 1
    kolikoLazenja = len(premiki) - kolikoMirovanj 
    return (kolikoLazenja, round((x**2 + y**2)**0.5, 2))

3. podnaloga

Po končanem tekmovanju so znane poti vseh polžev. Velja, da je zmagovalec tisti polž, ki je prilezel najdlje (seveda pa si lahko prvo mesto deli več zmagovalcev, če so prelezli isto razdaljo). Pri tem seveda doseženo razdaljo računamo zaokroženo, kot pri zg. nalogi! Napišite funkcijo zmagovalci(s), ki sprejme seznam poti vseh polžev ter izračuna, koliko polžev je osvojilo prvo nagrado.

>>> zmagovalci([['S', 'Z', 'S'], ['M', 'Z', 'V'], ['J', 'J', 'Z'], ['J', 'J', 'Z'], ['S', 'S', 'M']])
3

Uradna rešitev

def zmagovalci(poti):
    '''Koliko polžev je zmagalo'''
    najRazdalja = -1 # vse poti bodo daljše
    kolikoZmagovalcev = 0
    for potPolza in poti:
        _, razdalja = pot(potPolza) # lazil nas načeloma ne zanima, zato "čudno" imen - le podčrtaj
        if razdalja > najRazdalja: # našli smo boljšega
            najRazdalja = razdalja
            kolikoZmagovalcev = 1 # začnemo šteti znova
        elif razdalja == najRazdalja:
            kolikoZmagovalcev += 1 # en več z naj razdaljo
    return kolikoZmagovalcev        

#
# z nekaj dodatnega znanja je zapis rešitve krajši
#
##def zmagovalci(s):
##    p = [pot(x) for x in s]
##    mx = max(p)
##    return p.count(mx)